//Copyright (C) 2007 James Ashley (www.imaginativeuniversal.com) under the terms of the LGPL.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Speech.Recognition;
using System.Speech.Synthesis;
using System.Threading;
using System.IO;
using System.Xml;
using System.Xml.Serialization;


namespace SAPISophia
{
    public partial class SAPISophiaUI : Form
    {
        #region private members
        
        protected SophiaBot.SAPIBot myBot;
        protected delegate void SafeWriteToOutputDelegate(string output);
        protected object myLock = new object();
        #endregion

        #region initialization and loadup

        private void SRMain_Load(object sender, EventArgs e)
        {

            string path = Application.ExecutablePath;
            string aimlpath = path.Substring(0, path.LastIndexOf("\\") + 1) + "AIML FILES\\";
            string gamedatapath = path.Substring(0, path.LastIndexOf("\\") + 1) + "Game Data\\Data\\";
            string savedgamespath = path.Substring(0, path.LastIndexOf("\\") + 1) + "Game Data\\Saves\\";


            //initialize SAPIBot
            myBot = new SophiaBot.SAPIBot(aimlpath, gamedatapath, savedgamespath);
            myBot.SpecializedLexiconStateChanged += new SophiaBot.GenericEventHandler<bool>(myBot_AIMLLexiconStateChanged);
            myBot.DictationStateChanged += new SophiaBot.GenericEventHandler<bool>(myBot_DictationStateChanged);
            myBot.TextToSpeechStateChanged += new SophiaBot.GenericEventHandler<bool>(myBot_TextToSpeechStateChanged);
            myBot.InputProcessed += new EventHandler(myBot_ThreadSafeInputProcessed);
            myBot.RecognizeText += new SophiaBot.GenericEventHandler<string>(myBot_ThreadSafeRecognizeText);
            myBot.RejectText += new SophiaBot.GenericEventHandler<string>(myBot_RejectText);
            myBot.GuessText += new SophiaBot.GenericEventHandler<string>(myBot_GuessText);
            myBot.WriteToOutput += new SophiaBot.GenericEventHandler<string>(myBot_ThreadSafeWriteToOutput);
            myBot.ChangeBackgroundColor += new SophiaBot.GenericEventHandler<Color>(myBot_ThreadSafeChangeBackgroundColor);
            myBot.ChangeFontColor += new SophiaBot.GenericEventHandler<Color>(myBot_ThreadSafeChangeFontColor);
            myBot.ChangeFont += new SophiaBot.GenericEventHandler<string>(myBot_ThreadSafeChangeFont);
            myBot.UpdateStatus += new SophiaBot.GenericEventHandler<string>(myBot_ThreadSafeUpdateStatus);

            //Initialize Menus
            LoadSelectVoiceMenu();
            BuildBackgroundColorsMenuItem(backgroundMenuItem);
            BuildForegroundColorsMenuItem(foregroundMenuItem);
            BuildOpacityMenuItem(opacityMenuItem);

            //set apearance
            DeserializeAppearance();


            if (!myBot.IsVistaOS())
            {
                DictationMenuItem.Enabled = false;
                AIMLLexiconMenuItem.Enabled = false;
            }


            myBot.ToggleTextToSpeech();
            this.richTextBoxOutput.AppendText(myBot.About + Environment.NewLine + Environment.NewLine);
            this.txtInput.Focus();
        }

        public SAPISophiaUI()
        {
            InitializeComponent();
        }

        #endregion

        #region sapibot event handlers

        void myBot_WriteToOutput(string outputText)
        {
            WriteToOutputWindow(outputText);
        }

        void myBot_ThreadSafeWriteToOutput(string outputText)
        {
            string[] args = new string[1] { outputText };
            this.Invoke(new SafeWriteToOutputDelegate(WriteToOutputWindow), args);
        }

        void myBot_TextToSpeechStateChanged(bool val)
        {
            SpeechSynthesisMenuItem.Checked = val;
        }

        void myBot_RejectText(string outputText)
        {
            RejectText(outputText);
        }

        void myBot_GuessText(string outputText)
        {
            GuessText(outputText);
        }

        void myBot_RecognizeText(string outputText)
        {
            DisplayRecognizedText(outputText);
        }

        void myBot_InputProcessed(object source, EventArgs e)
        {
            InputProcessed(source, e);
        }

        void myBot_ThreadSafeRecognizeText(string outputText)
        {
            object[] args = new object[1]{outputText};
            this.BeginInvoke(new SophiaBot.GenericEventHandler<string>(DisplayRecognizedText), args);
        }

        void myBot_ThreadSafeInputProcessed(object source, EventArgs e)
        {
            try
            {
                this.BeginInvoke(new EventHandler(InputProcessed));
            }
            catch { }
        }

        void myBot_DictationStateChanged(bool val)
        {
            DictationMenuItem.Checked = val;
        }

        void myBot_AIMLLexiconStateChanged(bool val)
        {
            AIMLLexiconMenuItem.Checked = val;
        }

        void myBot_ThreadSafeChangeFontColor(Color val)
        {
            Object[] args = new Object[1] { val };
            this.BeginInvoke(new SophiaBot.GenericEventHandler<Color>(ChangeFontColor), args);
        }

        void myBot_ChangeFontColor(Color val)
        {
            ChangeFontColor(val);
        }

        void myBot_ThreadSafeChangeBackgroundColor(Color val)
        {
            Object[] args = new Object[1] { val };
            this.BeginInvoke(new SophiaBot.GenericEventHandler<Color>(ChangeBackgroundColor), args);
        }

        void myBot_ChangeBackgroundColor(Color val)
        {
            ChangeBackgroundColor(val);
        }

        void myBot_ThreadSafeChangeFont(string val)
        {
            Object[] args = new Object[1] { val };
            this.BeginInvoke(new SophiaBot.GenericEventHandler<string>(ChangeFont), args);
        }

        void myBot_ChangeFont(string val)
        {
            ChangeFont(val);
        }

        void UpdateStatus(string status)
        {
            try
            {
                toolStripStatusLabel2.Text = status;
                this.statusStrip1.Refresh();
            }
            catch { }
        }

        void myBot_ThreadSafeUpdateStatus(string status)
        {
            try
            {
                object[] args = new object[1] { status };
                this.BeginInvoke(new SophiaBot.GenericEventHandler<string>(UpdateStatus), args);
            }
            catch { }
        }

        #endregion

        #region enter button, text display

        private void btnEnter_Click(object sender, EventArgs e)
        {
            myBot.Read(this.txtInput.Text);
            this.txtInput.Text = string.Empty;
        }

        private void DisplayRecognizedText(string text)
        {
            try
            {
                SpeechStatusLabel.Text = text;
                SpeechStatusLabel.ForeColor = Color.Honeydew;
                this.statusStrip1.Refresh();
            }
            catch { }
        }

        private void GuessText(string guess)
        {
            try
            {
                SpeechStatusLabel.Text = guess;
                SpeechStatusLabel.ForeColor = Color.DarkSalmon;
                this.statusStrip1.Refresh();
            }
            catch { }
        }

        private void RejectText(string rejected)
        {
            try
            {
                SpeechStatusLabel.Text = rejected;
                SpeechStatusLabel.ForeColor = Color.Maroon;
                this.statusStrip1.Refresh();
            }
            catch { }
        }

        private void WriteToOutputWindow(string outputText)
        {
            try
            {
                lock (myLock)
                {
                    this.richTextBoxOutput.AppendText(outputText);
                    ScrollDown();
                }
            }
            catch {}
        }


        private void ChangeFontColor(Color val)
        {
            try
            {
                richTextBoxOutput.ForeColor = val;
                txtInput.ForeColor = val;
                txtCaret.ForeColor = val;
                foreach (MenuItem m in foregroundMenuItem.MenuItems)
                {
                    if (m.Text.Equals(val.Name))
                        m.Checked = true;
                    else
                        m.Checked = false;
                }
            }
            catch { }
        }


        private void ChangeBackgroundColor(Color val)
        {
            try
            {
                richTextBoxOutput.BackColor = val;
                panel1.BackColor = val;
                txtInput.BackColor = val;
                txtCaret.BackColor = val;

                foreach (MenuItem m in backgroundMenuItem.MenuItems)
                {
                    if (m.Text.Equals(val.Name))
                        m.Checked = true;
                    else
                        m.Checked = false;
                }
            }
            catch { }
        }

        private void ChangeFont(string fontName)
        {
            try
            {
                Font font = new Font(fontName, richTextBoxOutput.Font.Size);
                richTextBoxOutput.Font = font;
                txtInput.Font = font;
            }
            catch(Exception ex)
            { WriteToOutputWindow(ex.Message); }
        }

        private void ChangeFont(Font font)
        {
            try
            {
                richTextBoxOutput.Font = font;
                txtInput.Font = font;
            }
            catch (Exception ex)
            { WriteToOutputWindow(ex.Message); }
        }

        private void InputProcessed(object source, EventArgs e)
        {
            try
            {
                txtInput.Focus();
            }
            catch { }
        }

        #endregion

        #region menu item handlers

        private void voiceMenuItem_Click(object sender, EventArgs e)
        {
            SelectVoice(sender);
        }

        public MenuItem DictationMenuItem
        {
            get { return this.menuItem4; }
        }

        private MenuItem AIMLLexiconMenuItem
        {
            get { return this.menuItem7; }
        }

        public MenuItem SpeechSynthesisMenuItem
        {
            get { return this.menuItem3; }
        }

        public ToolStripStatusLabel SpeechStatusLabel
        {
            get { return toolStripStatusLabel1; }
        }

        private void menuItem3_Click(object sender, EventArgs e)
        {
            ToggleTextToSpeech();
        }

        private void menuItem4_Click(object sender, EventArgs e)
        {
            ToggleDictation();
        }

        private void menuItem5_Click(object sender, EventArgs e)
        {
            SaveAs();
        }

        private void menuItem6_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void menuItem7_Click(object sender, EventArgs e)
        {
            ToggleAIMLLexicon();
        }

        private void fontMenuItem_Click(object sender, EventArgs e)
        {
            fontDialog1.Font = richTextBoxOutput.Font;
            fontDialog1.Color = richTextBoxOutput.ForeColor;
            fontDialog1.ShowColor = false;
            fontDialog1.ShowEffects = true;
            fontDialog1.ShowDialog();
            richTextBoxOutput.Font = fontDialog1.Font;
            this.txtInput.Font = fontDialog1.Font;
        }

        #endregion

        #region build appearance menu items

        /// <summary>
        /// Builds the colors menu item.
        /// </summary>
        /// <param name="mi">The mi.</param>
        protected void BuildBackgroundColorsMenuItem(MenuItem mi)
        {
            foreach (string colorName in System.Enum.GetNames(typeof(KnownColor)))
            {
                    MenuItem m = new MenuItem(colorName);
                    m.Click += new EventHandler(Background_Select);
                    backgroundMenuItem.MenuItems.Add(m);
            }
        }

        /// <summary>
        /// Builds the colors menu item.
        /// </summary>
        /// <param name="mi">The mi.</param>
        protected void BuildForegroundColorsMenuItem(MenuItem mi)
        {
            foreach (string colorName in System.Enum.GetNames(typeof(KnownColor)))
            {
                MenuItem m = new MenuItem(colorName);
                m.Click += new EventHandler(Foreground_Select);
                foregroundMenuItem.MenuItems.Add(m);
            }
        }


        /// <summary>
        /// Builds the opacity menu item.
        /// </summary>
        /// <param name="mi">The mi.</param>
        protected void BuildOpacityMenuItem(MenuItem mi)
        {
            for(int i = 20; i > 0; i--)
            {
                MenuItem m = new MenuItem((i*5).ToString());
                m.Click += new EventHandler(Opacity_Select);
                opacityMenuItem.MenuItems.Add(m);
            }
        }

        void Background_Select(object sender, EventArgs e)
        {
            MenuItem thisMenuItem = (MenuItem)sender;
            string color = thisMenuItem.Text;
            if (color == "Transparent")
                ChangeBackgroundColor(this.TransparencyKey);
            else
                ChangeBackgroundColor(Color.FromName(color));
        }

        void Foreground_Select(object sender, EventArgs e)
        {
            MenuItem thisMenuItem = (MenuItem)sender;
            string color = thisMenuItem.Text;
            if (color == "Transparent")
                ChangeFontColor(this.TransparencyKey);
            else
                ChangeFontColor(Color.FromName(color));

        }

        void Opacity_Select(object sender, EventArgs e)
        {
            MenuItem thisMenuItem = (MenuItem)sender;
            Set_Opacity( double.Parse(thisMenuItem.Text));
        }

        void Set_Opacity(double o)
        {
            if (o == 0)
                this.Opacity = 0;
            else if (o > 1)
                this.Opacity = o / 100;
            else
                this.Opacity = o;

            foreach (MenuItem m in opacityMenuItem.MenuItems)
            {
                if (m.Text.Equals((this.Opacity * 100).ToString()))
                    m.Checked = true;
                else
                    m.Checked = false;
            }
        }

        #endregion

        #region ui helper methods

        #region save methods

        private void SaveAs(string fileName)
        {
            try
            {
                saveFileDialog1.FileName = fileName;
                DialogResult dr = saveFileDialog1.ShowDialog();
                if (dr == DialogResult.Cancel)
                {
                    return;
                }
                string saveFileName = saveFileDialog1.FileName;
                Save(saveFileName);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void SaveAs()
        {
            string fileName = "*.txt";
            SaveAs(fileName);
        }

        internal void Save()
        {
            string fileName = "*.txt";
            Save(fileName);
        }

        private void Save(string fileName)
        {
            string text = this.richTextBoxOutput.Text;
            Save(fileName, text);
        }

        private void Save(string fileName, string text)
        {
            try
            {
                using (StreamWriter sw = new StreamWriter(fileName, false))
                {
                    sw.Write(text);
                    sw.Flush();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        #endregion

        #region toggle menuitems

        private void ToggleDictation()
        {
            myBot.ToggleDictation();
        }

        private void ToggleAIMLLexicon()
        {
            myBot.ToggleSpecializedLexicon();
        }

        private void ToggleTextToSpeech()
        {
            myBot.ToggleTextToSpeech();
        }

        #endregion

        #region select voice

        private void LoadSelectVoiceMenu()
        {
            foreach (string voice in myBot.VoiceList)
            {
                MenuItem voiceMenuItem = new MenuItem(voice);
                voiceMenuItem.RadioCheck = true;
                voiceMenuItem.Click += new EventHandler(voiceMenuItem_Click);
                this.selectVoiceMenuItem.MenuItems.Add(voiceMenuItem);
            }
            if (this.selectVoiceMenuItem.MenuItems.Count > 0)
            {
                this.selectVoiceMenuItem.MenuItems[0].Checked = true;
                myBot.SelectedVoice = selectVoiceMenuItem.MenuItems[0].Text;
            }
        }

        private void SelectVoice(object sender)
        {
            MenuItem mi = sender as MenuItem;
            if (mi != null)
            {
                //toggle checked value
                mi.Checked = !mi.Checked;

                if (mi.Checked)
                {
                    //set selectedVoice variable
                    myBot.SelectedVoice = mi.Text;
                    //clear all other checked items
                    foreach (MenuItem voiceMi in this.selectVoiceMenuItem.MenuItems)
                    {
                        if (!voiceMi.Equals(mi))
                        {
                            voiceMi.Checked = false;
                        }
                    }
                }
                else
                {
                    //if deselecting, make first value checked, 
                    //so there is always a default value
                    this.selectVoiceMenuItem.MenuItems[0].Checked = true;
                }
            }
        }

        #endregion

        #region refresh output window

        private void ScrollDown()
        {
            this.richTextBoxOutput.Refresh();
            this.richTextBoxOutput.Focus();
        }

        #endregion

        #region load and save appearance settings

        private void SAPISophiaUI_FormClosing(object sender, FormClosingEventArgs e)
        {
            SerializeAppearance();
        }

        private void SerializeAppearance()
        {
            string currentFont = richTextBoxOutput.Font.Name;
            float currentSize = richTextBoxOutput.Font.Size;
            FontStyle currentStyle = richTextBoxOutput.Font.Style;
            string currentForeColor = richTextBoxOutput.ForeColor.Name;
            string currentBackColor = richTextBoxOutput.BackColor.Name;
            double currentOpacity = this.Opacity;
            AppearanceSettings appearance = new AppearanceSettings(currentFont, currentSize, currentStyle,currentForeColor, currentBackColor, currentOpacity);
            XmlSerializer s = new XmlSerializer(typeof(AppearanceSettings));
            using (TextWriter w = new StreamWriter(@"appearance.xml"))
            {
                s.Serialize(w, appearance);
            }
        }

        private void DeserializeAppearance()
        {
            AppearanceSettings appearance;

            if (File.Exists("appearance.xml"))
            {
                try
                {
                    XmlSerializer s = new XmlSerializer(typeof(AppearanceSettings));
                    using (TextReader r = new StreamReader("appearance.xml"))
                    {
                        appearance = (AppearanceSettings)s.Deserialize(r);
                    }
                   
                }
                catch
                {
                    appearance = AppearanceSettings.DefaultSettings();
                }
            }
            else
            {
                appearance = AppearanceSettings.DefaultSettings();
            }

            ChangeFont(new Font(appearance.Font, appearance.Size, appearance.Style));
            ChangeFontColor(Color.FromName(appearance.ForegroundColor));
            ChangeBackgroundColor(Color.FromName(appearance.BackgroundColor));
            Set_Opacity(appearance.Opacity);
        }

        public struct AppearanceSettings
        {
            public string Font;
            public float Size;
            public FontStyle Style;
            public string ForegroundColor;
            public string BackgroundColor;
            public double Opacity;

            public AppearanceSettings(string font, float size, FontStyle style, string foregroundColor, string backgroundColor, double opacity)
            {
                this.Font = font;
                this.Size = size;
                this.Style = style;
                this.ForegroundColor = foregroundColor;
                this.BackgroundColor = backgroundColor;
                this.Opacity = opacity;
            }

            static public AppearanceSettings DefaultSettings()
            {
                double opacity = 1d;
                if (Environment.OSVersion.Version.Major >= 6)
                    opacity = .85d;

                return new AppearanceSettings("Courier New", 12, FontStyle.Regular, "Green", "Black", opacity);
            }

            static public AppearanceSettings GrampaSettings()
            {
                double opacity = 1d;
                return new AppearanceSettings("Courier New", 12, FontStyle.Regular, "Green", "Black", opacity);
            }

            static public AppearanceSettings MatrixSettings()
            {
                double opacity = .8d;
                return new AppearanceSettings("Arial", 10, FontStyle.Regular, "LimeGreen", "Black", opacity);
            }

            static public AppearanceSettings ChalkboardSettings()
            {
                return new AppearanceSettings("Papyrus", 14, FontStyle.Regular, "Gainsboro", "DarkSlateGray", 1d);
            }

            static public AppearanceSettings FountainPenSettings()
            {
                return new AppearanceSettings("Vivaldi", 16, FontStyle.Italic, "Azure", "Orchid", 1d);
            }

            static public AppearanceSettings BlackAndWhiteSettings()
            {
                return new AppearanceSettings("Verdana", 12, FontStyle.Regular, "Black", "White", 1d);
            }


            static public AppearanceSettings OldEnglishSettings()
            {
                return new AppearanceSettings("Old English Text MT", 16, FontStyle.Regular, "Yellow", "Maroon", 1d);
            }
        }

        private void iBMXTThemeMenuItem_Click(object sender, EventArgs e)
        {
            AppearanceSettings appearance = AppearanceSettings.GrampaSettings();
            ChangeFont(new Font(appearance.Font, appearance.Size, appearance.Style));
            ChangeFontColor(Color.FromName(appearance.ForegroundColor));
            ChangeBackgroundColor(Color.FromName(appearance.BackgroundColor));
            Set_Opacity(appearance.Opacity);
        }

        private void theMatrixThemeMenuItem_Click(object sender, EventArgs e)
        {
            AppearanceSettings appearance = AppearanceSettings.MatrixSettings();
            ChangeFont(new Font(appearance.Font, appearance.Size, appearance.Style));
            ChangeFontColor(Color.FromName(appearance.ForegroundColor));
            ChangeBackgroundColor(Color.FromName(appearance.BackgroundColor));
            Set_Opacity(appearance.Opacity);
        }

        private void blackboardMenuItem_Click(object sender, EventArgs e)
        {
            AppearanceSettings appearance = AppearanceSettings.ChalkboardSettings();
            ChangeFont(new Font(appearance.Font, appearance.Size, appearance.Style));
            ChangeFontColor(Color.FromName(appearance.ForegroundColor));
            ChangeBackgroundColor(Color.FromName(appearance.BackgroundColor));
            Set_Opacity(appearance.Opacity);

        }

        private void fountainPenMenuItem_Click(object sender, EventArgs e)
        {
            AppearanceSettings appearance = AppearanceSettings.FountainPenSettings();
            ChangeFont(new Font(appearance.Font, appearance.Size, appearance.Style));
            ChangeFontColor(Color.FromName(appearance.ForegroundColor));
            ChangeBackgroundColor(Color.FromName(appearance.BackgroundColor));
            Set_Opacity(appearance.Opacity);
        }

        private void blackAndWhiteMenuItem_Click(object sender, EventArgs e)
        {
            AppearanceSettings appearance = AppearanceSettings.BlackAndWhiteSettings();
            ChangeFont(new Font(appearance.Font, appearance.Size, appearance.Style));
            ChangeFontColor(Color.FromName(appearance.ForegroundColor));
            ChangeBackgroundColor(Color.FromName(appearance.BackgroundColor));
            Set_Opacity(appearance.Opacity);
        }

        private void oldEnglishMenuItem_Click(object sender, EventArgs e)
        {
            AppearanceSettings appearance = AppearanceSettings.OldEnglishSettings();
            ChangeFont(new Font(appearance.Font, appearance.Size, appearance.Style));
            ChangeFontColor(Color.FromName(appearance.ForegroundColor));
            ChangeBackgroundColor(Color.FromName(appearance.BackgroundColor));
            Set_Opacity(appearance.Opacity);
        }

        #endregion



        #endregion

    }
}
